/*
    This file is part of the Time-of-Flight Tracer program. It is not part of
    the original PBRT source distribution. See the included license file for
    more information.

    Copyright(c) 2014 Microsoft Corporation

    Author: Phil Pitts
*/

#include "stdafx.h"
#include "film/backscatter/histogram.h"


HistogramFilm::HistogramFilm(int xres, int yres, const string &fn, float size, 
	float range, float minL) : 
	BackscatterFilm(xres, yres, fn) {

	if (size <= 0.f) {
		Severe("Illegal bin size");
	}

	minimumL = minL;
	binSize = size;
	averageDistance = false;

	nBins = static_cast<int>(range / size);

	pixels = new BlockedArray<Histogram*>(xres, yres);
}


HistogramFilm::~HistogramFilm() {
	for (int x = 0; x < xResolution; x++) {
		for (int y = 0; y < yResolution; y++) {
			Histogram* pixel = (*pixels)(x, y);
			if (pixel) delete pixel;
		}
	}
	delete pixels;
}


void HistogramFilm::AddSample(const CameraSample &sample, const PaintData &paint) {
	int x = Floor2Int(sample.imageX), y = Floor2Int(sample.imageY);

	// Address floating point error for values close to pixel resolution
	// Ex. 99.999999 --> (Error) --> 100.000000 --> (Floor) --> 100
	if (x == xResolution) x--;
	if (y == yResolution) y--;

	if (!(*pixels)(x, y)) (*pixels)(x, y) = new Histogram(nBins, binSize);
	Histogram *pixel = (*pixels)(x, y);

	bool contributed = false;
	for (int i = 0; i < paint.bounces.size(); i++)
	{
		int index = static_cast<int>(paint.bounces[i].distance / binSize);
		if (index < nBins) {
			AtomicAdd(&pixel->bins[index].radiance, paint.bounces[i].radiance);
			contributed = true;
		}
	}
	if (contributed) AtomicAdd(&pixel->nsamples, 1);
}


void HistogramFilm::Splat(const CameraSample &sample, const PaintData &paint) {
	averageDistance = true;
	int x = Floor2Int(sample.imageX), y = Floor2Int(sample.imageY);

	// Address floating point error for values close to pixel resolution
	// Ex. 99.999999 --> (Error) --> 100.000000 --> (Floor) --> 100
	if (x == xResolution) x--;
	if (y == yResolution) y--;

	if (!(*pixels)(x, y)) (*pixels)(x, y) = new Histogram();
	Histogram *pixel = (*pixels)(x, y);

	// Only use the first bin since we'll add the distance contributions
	// to get an average distance for the pixel
    nBins = 1;
	for (int i = 0; i < paint.bounces.size(); i++) {
		AtomicAdd(&pixel->bins[0].radiance, paint.bounces[i].radiance);
		AtomicAdd(&pixel->bins[0].distance, paint.bounces[i].distance);
	}
	AtomicAdd(&pixel->nsamples, 1);
}

void HistogramFilm::WriteImage(float splatScale) {
	FILE* fp = fopen(filename.c_str(), "w");
	if (!fp) {
		Severe("Film file %s could not be opened", filename);
	}

	for (int i = 0; i < xResolution; i++) {
		for (int j = 0; j < yResolution; j++) {
			Histogram *pixel = (*pixels)(i, j);

			if (!pixel) continue;

			bool isFirst = true;
			for (int n = 0; n < nBins; n++) {
				HistogramSample& sample = pixel->bins[n];
				sample.radiance /= pixel->nsamples;

				if (averageDistance)
					sample.distance /= pixel->nsamples;

				if (sample.radiance >= minimumL) {
					if (isFirst) {
						fprintf(fp, "# %d %d ", i, j);
						isFirst = false;
					}
					fprintf(fp, "%f %f ", sample.distance, sample.radiance);
				}
			}
		}
	}

	fclose(fp);
}


HistogramFilm *CreateHistogramFilm(const ParamSet &params) {
	int xres, yres;
	string filename;

	GetBackscatterParams(params, &xres, &yres, &filename);

	float minL = params.FindOneFloat("minimumL", 0.f);
	float range = params.FindOneFloat("range", 1.f);
	float stepSize = params.FindOneFloat("binsize", 0.1);

	return new HistogramFilm(xres, yres, filename, stepSize, range, minL);
}